-- 登录服务附属节点
-- Author: Colocasia
-- Date: 2019-05-01

local skynet = require 'skynet';
require 'skynet.manager';
local crypt = require 'skynet.crypt';
local string = require 'us.string';
local table = require 'us.table';
local socket = require 'us_socket';
local netpack = require 'us_netpack';
local us_rpc = require 'us_rpc';
local timer = require 'timer';
local handler = require 'handler';
local account = require 'account';
local log = require 'log';


local SEND_SESSION_NO_RET = 0

-- socket 处理客户端消息
local SOCKET = {}
-- CMD 处理服务器消息
local CMD = {}

-- 参数列表
local args = {...}
local master = args[1] and tonumber(args[1]) or nil;

local authlist = {};
local loginservice = {};

local server_list;

-- cdn
local cdn = 
{
    name = "cdn",
    host = skynet.getenv "cdn_host",
    port = tonumber(skynet.getenv "cdn_port"),
}


-- 连接建立
function loginservice.client_conneted(fd)
    if nil == fd then return end
    local u = {};
    u.challenge = crypt.randomkey();
    u.server_key = crypt.randomkey();
    return u;
end

-- 处理握手
function loginservice.handshake(user, msg)
    if not msg then return end

    local recvkey = crypt.base64decode(msg.client_key);
    if user.client_key == recvkey then
        log.Warning('repeated handshake');
        return;
    end

    log.info('client handshake ok')
    user.client_key = recvkey;
    user.secret = crypt.dhsecret(user.client_key, user.server_key);

    local msg = {
        server_key = crypt.base64encode(crypt.dhexchange(user.server_key));
        challenge = crypt.base64encode(user.challenge);
    }

    return handler:render(msg);
end

-- 解析acc_info
local function parse_acc_info(acc_info, secret)
    acc_info = crypt.desdecode(secret, acc_info)
    local t_acc = string.split(acc_info, '@');
    if not t_acc or #t_acc ~= 3 then return end

    local acc_name = crypt.base64decode(t_acc[1]);
    local acc_pwd = crypt.base64decode(t_acc[2]);
    return acc_name, acc_pwd;
end

-- 发送登录成功的token到gate
local function send_token_to_gate(token)
    for _, s in pairs(server_list or {}) do
        skynet.send(s.watchdog, "lua", "redirect", token);
    end
end

-- 处理认证信息
function loginservice.auth(user, msg)
    local acc_secret = crypt.base64decode(msg.acc_secret);
    local acc_info = crypt.base64decode(msg.acc_info);
    if not acc_info or not acc_secret then
        log.printf('invalid_params');
        return handler:code('invalid_params');
    end

    local hmac = crypt.hmac64(user.challenge, user.secret);
    if hmac ~= acc_secret then
        log.info("[AUTH]auth account failed: %s!", "invalid secret");
        return handler:code('account_info_invalid');
    end

    local acc_name, acc_pwd = parse_acc_info(acc_info, user.secret)
    if not acc_name or not acc_pwd then
        log.info("[AUTH]auth account failed: %s!", "invalid account name or password");
        return handler:code('account_info_invalid');
    end

    local result = account:auth(acc_name, acc_pwd);
    if not result.code or result.code.opcode > 0 then
        log.info("[AUTH]auth account failed: %s!", result.code and result.code.opmsg or "unknow");
        return result;
    end

    log.info("[AUTH]auth account success: acc_name->'%s', acc_pwd->'%s'!", acc_name, acc_pwd);
    send_token_to_gate(result.token);

    table.merge({result, {
        server_list = server_list,
        cdn = cdn,
    }})

    return result;
end

-- 注册新的账号
function loginservice.register(user, msg)
    local acc_secret = crypt.base64decode(msg.acc_secret);
    local acc_info = crypt.base64decode(msg.acc_info);
    if not acc_info or not acc_secret then
        log.printf('invalid_params');
        return handler:code('invalid_params');
    end

    local hmac = crypt.hmac64(user.challenge, user.secret);
    if hmac ~= acc_secret then
        return handler:code('account_info_invalid');
    end

    local acc_name, acc_pwd = parse_acc_info(acc_info, user.secret)
    if not acc_name or not acc_pwd then
        return handler:code('account_info_invalid');
    end

    local result = account:register(acc_name, acc_pwd);
    if 0 ~= result.code.opcode then
        return result;
    end

    -- log.dump(result, "result");
    local acc_id = result.acc_id;
    if not msg.auto_auth then
        return result;
    end

    acc = account:find_account(acc_id);
    if not acc then
        return handler:code('account_not_exist');
    end

    local token = account:gen_token(acc.guid);
    send_token_to_gate(token);

    return handler:render({
        token = token,
        server_list = server_list,
        cdn = cdn,
    })
end

---测试
---@return table
function loginservice.heartbeat(user, msg)
    local ret =
    {
        client_info = {
            client_time = msg.client_info.client_time;
        },
        server_info = {
            server_time = os.time();
        },
    }
    return handler:render(ret);
end

-- 发送回应
local function send_package(fd, pack)
    if not fd then return end
	socket.send(fd, pack)
end

-- 处理请求
local function request(fd, name, msg, response)
    log.info(string.format("CLIENT-MSG request = %s", name));
    local f = assert(loginservice[name]);
    local u = assert(authlist[fd]);
    local r = f(u, msg) or {}
    -- log.dump(r, "msg");
    if response then
        log.info(string.format("CLIENT-MSG response = %s", name));
        return response(r);
    else
        log.warning(string.format("CLIENT-MSG ERROR, NO RESPONSE = %s", name));
    end
end

-- 处理请求异常
local function on_request_error(err)
    log.error(err);
end

-- 连接成功
function CMD.connect(fd, addr)
    log.printf("connect fd->%s, addr->%s", fd, addr);
    if not master then
        return false
    end

    local ret = skynet.call(master, "lua", "open_client", fd);
    if not ret then
        return false
    end

    -- timer.add_timer("timeout"..fd, 1, false, function()
    --     log.info("timeout fd:%s", fd);
    --     skynet.send(master, "lua", "close_client", fd);
    -- end);

    authlist[fd] = loginservice.client_conneted(fd);
    authlist[fd].addr = addr;
end

-- 断开链接
function CMD.disconnect(fd)
    local cn = authlist[fd];
    if not cn then
        log.printf("socket invalid! fd (%s)", fd)
    end

    log.info("[Login]socket client dissconnect! fd==>%s addr==>%s", fd, cn.addr);
    authlist[fd] = nil;
end

-- 更新列表
function CMD.update_server_list(list)
    -- log.dump(list, "master list");
    server_list = list;
end

-- 错误处理
function on_cmd_err()
    log.error("loginservice execute cmd error");
end

-- 加载slave
function loginservice.lanch_slave()
    -- log.printf("=====>lanch_slave<=====");
    skynet.dispatch("lua", function(session, source, cmd, ...)
        local f = assert(CMD[cmd], "loginservice cmd=>"..cmd.." not found");
        local function ret(ok, ...)
            if session ~= SEND_SESSION_NO_RET then
                skynet.retpack (...)
            end
        end
        ret(xpcall(f, on_cmd_err, ...));
    end)
end

-- 注册客户端消息
skynet.register_protocol {
    name = "client",
    id = skynet.PTYPE_CLIENT,
    -- 解析网络包
    unpack = function (msg, sz)
        buffer = skynet.tostring(msg, sz);
        -- skynet.tostring不会释放内存这里需要注意调用skynet.trash(msg,sz)
        -- Mark: 调用trash会导致异常
        -- skynet.trash(msg, sz);
        -- print(buffer, msg, sz);
        return us_rpc:unpack(buffer, sz);
    end,
    -- 处理客户端消息
    dispatch = function (fd, _, type, ...)
        skynet.ignoreret()	-- session is fd, don't call skynet.ret
        -- skynet.trace()
        if 1 == type then       -- 请求
            local ok, result = xpcall(request, on_request_error, fd, ...);
            if ok then
                if result then
                    send_package(fd, result)
                end
            else
                skynet.error(result)
            end
        else                    -- 回应
    
        end
    end
}

skynet.start(loginservice.lanch_slave)
return loginservice;
